package com.msb.mallsearch.service.impl;

import com.alibaba.fastjson.JSON;
import com.msb.common.dao.es.SkuESModel;
import com.msb.mallsearch.config.MallElasticSearchConfiguration;
import com.msb.mallsearch.constant.ESconstant;
import com.msb.mallsearch.service.MallSearchService;
import com.msb.mallsearch.vo.SearchParam;
import com.msb.mallsearch.vo.SearchResult;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;


import javax.swing.text.Highlighter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Auther: xixifeng
 * @Date: 2022/10/30 - 10 - 30 - 14:14
 * @Description: com.msb.mallsearch.service.impl
 * @version: 1.0
 */
@Service
public class MallSearchServicelmpl implements MallSearchService {

    @Autowired
    RestHighLevelClient client;
    @Override
    public SearchResult search(SearchParam param) {
        // 1. 接收前端传过来的检索请求(就是写查询语句，第二步执行语句)
        SearchRequest request = buildSearchRequest(param);

        SearchResult result=null;
        try {
            // 2.执行检索操作(就是执行查询语句)
            SearchResponse  response = client.search(request, MallElasticSearchConfiguration.COMMON_OPTIONS);

            // 3.需要把检索的信息封装为SearchResult（把查询到的结果做一个封装，然后返回到页面）
            result = buildSearchResult(response,param);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return result;
    }


    /**
     * 构建检索的请求
     * 模糊匹配，关键字匹配
     * 过滤(类别，品牌，属性，价格区间，库存)
     * 排序
     * 分页
     * 高亮
     * 聚合分析
     * @param param
     * @return
     */
    private SearchRequest buildSearchRequest(SearchParam param) {
        SearchRequest searchRequest = new SearchRequest();
        searchRequest.indices(ESconstant.PRODUCT_INDEX);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        // 构建具体的检索的条件
        // 1.构建bool查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 1.1 关键字的条件
        if (!StringUtils.isEmpty(param.getKeyword())) {
            boolQuery.must(QueryBuilders.matchQuery("subTitle", param.getKeyword()));
        }
        // 1.2 类别的检索条件
        if (param.getCatalog3Id()!=null) {
            boolQuery.filter(QueryBuilders.termQuery("catalogId", param.getCatalog3Id()));
        }
        // 1.3 品牌的检索条件
        List<Long> brandId = param.getBrandId();
        if (brandId != null && brandId.size() > 0) {
            boolQuery.filter(QueryBuilders.termsQuery("brandId", brandId));
        }
        // 1.4 是否有库存
        if (param.getHasStock()!=null) {

            boolQuery.filter(QueryBuilders.termsQuery("hasStock", param.getHasStock() == 1));
        }

        // 1.5 根据价格区间来检索
        if (!StringUtils.isEmpty(param.getSkuPrice())) {
            //我们首先需要根据 _ 做分割
            String[] s = param.getSkuPrice().split("_");
            RangeQueryBuilder skuPrice = QueryBuilders.rangeQuery("skuPrice");

            if (s.length == 2) {
                //说明是200_300
                skuPrice.gte(s[0]);
                skuPrice.lte(s[1]);
            } else if (s.length == 1) {
                //说明是 200_  或者 _300
                if (param.getSkuPrice().endsWith("_")) {
                    //说明是200_
                    skuPrice.gte(s[0]);
                }

                if (param.getSkuPrice().startsWith("_")) {
                    //说明是_300
                    skuPrice.lte(s[0]);
                }
            }
            boolQuery.filter(skuPrice);
        }

        // 1.6 属性的检索条件 attrs=20_8英寸:10英寸 & attrs=19_64GB:32GB
        if (param.getAttrs() != null && param.getAttrs().size() > 0) {
           //attrs=20_8英寸:10英寸
            for (String attr : param.getAttrs()) {

               BoolQueryBuilder boolNestQuery = QueryBuilders.boolQuery();

                //我们首先需要根据 _ 做分割
                String[] attrStrArray = attr.split("_");

                //获取属性的编号
                String attrId = attrStrArray[0];

                // 64GB:32GB  获取属性的值
                String[] attrValue = attrStrArray[1].split(":");
                // 拼接组合条件 nested的作用之一
                boolNestQuery.must(QueryBuilders.termQuery("attrs.attrId", attrId));
                boolNestQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValue));

                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", boolNestQuery, ScoreMode.None);

                boolQuery.filter(nestedQuery);

            }
        }
                // TODO 这里语句都是query部分
                searchSourceBuilder.query(boolQuery);

        // 2.排序
        /**
         * sort=salaCount_asc/desc 销量
         * sort=skuPrice_asc/desc 价格
         * sort=hotScore_asc/desc 是否库存
         */
        if (!StringUtils.isEmpty(param.getSort())) {
            //sort=hotScore_asc/desc 是否库存
            String[] s = param.getSort().split("_");
            SortOrder sortOrder = ("asc").equalsIgnoreCase(s[1]) ? SortOrder.ASC : SortOrder.DESC;
            searchSourceBuilder.sort(s[0], sortOrder);
        }

        // 3.处理分页
        if (param.getPageNum() != null) {
            /*
            * pageNum 当前页
            * size 每页的大小（就是每页展示多少数据）
            * from 从总的数据中第几条数据开始
            * */

            //pageNum=1  size=5  from=[0,1,2,3,4]
            //pageUnm=2  size=5  from=[5,6,7,8,9]
            //from = ( pageNum - 1 ) * pageSize
            searchSourceBuilder.from((param.getPageNum()-1)*ESconstant.PRODUCT_PAGESIZE);
            searchSourceBuilder.size(ESconstant.PRODUCT_PAGESIZE);
        }

        // 4. 设置高亮
        if (!StringUtils.isEmpty(param.getKeyword())) {
            // 如果有根据关键字查询那么我们才需要高亮设置
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("subTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            searchSourceBuilder.highlighter(highlightBuilder);
        }

        // 5.聚合运算
        // 5.1 品牌的聚合
            TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
            brand_agg.field("brandId");
            brand_agg.size(50);
        // 品牌的子聚合
        brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(10));
        brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(10));
        searchSourceBuilder.aggregation(brand_agg);

        // 5.2 类别的聚合
         TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg");
        catalog_agg.field("catalogId");
        catalog_agg.size(10);
        //类别的子聚合
        catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(10));
        searchSourceBuilder.aggregation(catalog_agg);

        // 5.3 属性的聚合

        NestedAggregationBuilder nestedAggregation = AggregationBuilders.nested("attr_agg", "attrs");


        // 属性id聚合
        TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg");
        attr_id_agg.field("attrs.attrId");
        attr_id_agg.size(10);

        // 属性id下的子聚合 属性名称和属性值
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(10));
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(10));
        nestedAggregation.subAggregation(attr_id_agg);
        searchSourceBuilder.aggregation(nestedAggregation);

        //todo 打印检索信息
        System.out.println(searchSourceBuilder.toString());

        //把所有的语句封装到searchRequest，然后返回
        searchRequest.source(searchSourceBuilder);

        return searchRequest;
    }

    /**
     * 根据检索的结果解析封装为SearchResult对象
     * @param response
     * @return
     */
    private SearchResult buildSearchResult(SearchResponse response, SearchParam param) {

        SearchHits hits = response.getHits();
        SearchResult result = new SearchResult();
        // 1.检索的所有商品信息
        SearchHit[] products = hits.getHits();
        List<SkuESModel> list = new ArrayList<>();
        if (products != null && products.length > 0) {
            //TODO 由于在商品上架的时候我们把商品转成了json数据，所以现在需要把它转回来
            for (SearchHit product : products) {
                //json数据
                String sourceAsString = product.getSourceAsString();
                //json数据转换成SkuESModel对象
                SkuESModel model =  JSON.parseObject(sourceAsString, SkuESModel.class);
                if(!StringUtils.isEmpty(param.getKeyword())){
                    // 我们需要设置高亮
                    HighlightField subTitle = product.getHighlightFields().get("subTitle");
                    String subTitleHighlight = subTitle.getFragments()[0].string();
                    model.setSubTitle(subTitleHighlight); // 设置高亮
                }
                list.add(model);
            }
        }
        result.setProducts(list);//设置商品集合

        // 2.当前商品所涉及到的所有的品牌
        Aggregations aggregations = response.getAggregations();
       ParsedLongTerms brand_agg = aggregations.get("brand_agg");
       List<? extends Terms.Bucket> buckets = brand_agg.getBuckets();//所以品牌的信息
        List<SearchResult.BrandVO> brandVOS = new ArrayList<>();//用来储存品牌的集合
        // 存储所有品牌的容器
        if (buckets != null && buckets.size() > 0) {
            for (Terms.Bucket bucket : buckets) {
                SearchResult.BrandVO brandVO = new SearchResult.BrandVO();
                String keyAsString = bucket.getKeyAsString();//这里获取的key是string类型
                brandVO.setBrandId(Long.parseLong(keyAsString));//把key转化为long类型，并设置
                // 然后我们需要获取品牌的名称和图片的地址
               ParsedStringTerms brand_img_agg = bucket.getAggregations().get("brand_img_agg");
                List<? extends Terms.Bucket> bucketsImg = brand_img_agg.getBuckets();
                if (bucketsImg != null && bucketsImg.size() > 0) {
                    //todo 现在是每个品牌只有一个图片地址，所以不需要循环，要是后面开发一个品牌有多个图片地址，可以在这里写个循环
                    String img = bucketsImg.get(0).getKeyAsString();
                    brandVO.setBrandImg(img);//设置图片，
                }
                // 获取品牌名称的信息
                ParsedStringTerms brand_name_agg = bucket.getAggregations().get("brand_name_agg");
                List<? extends Terms.Bucket> breadName = brand_name_agg.getBuckets();
                if (breadName != null && breadName.size() > 0) {
                    //todo 现在是每个品牌只有一个名称，所以不需要循环，要是后面开发一个品牌有多个名称，可以在这里写个循环
                    String name = breadName.get(0).getKeyAsString();
                    brandVO.setBrandName(name);//设置名称
                }
                brandVOS.add(brandVO);
            }
        }
        result.setBrands(brandVOS);//设置品牌的集合
        // 3.当前商品涉及到的所有的类别信息
        ParsedLongTerms catalog_agg = aggregations.get("catalog_agg");
        List<? extends Terms.Bucket> bucketsCatalogs = catalog_agg.getBuckets();
        List<SearchResult.CatalogVO> Catalogs=new ArrayList<>();
        if (bucketsCatalogs != null && bucketsCatalogs.size() > 0) {
            for (Terms.Bucket bucketsCatalog : bucketsCatalogs) {
                SearchResult.CatalogVO catalogVO = new SearchResult.CatalogVO();
                //获取类别的id编号
                String keyAsString = bucketsCatalog.getKeyAsString();
                catalogVO.setCatalogId(Long.parseLong(keyAsString));//设置类别id编号
                //获取类别的名称
                ParsedStringTerms catalog_name_agg = bucketsCatalog.getAggregations().get("catalog_name_agg");
                List<? extends Terms.Bucket> catelogname = catalog_name_agg.getBuckets();
                if (catelogname != null && catelogname.size() > 0) {

                   String name = catelogname.get(0).getKeyAsString();
                   catalogVO.setCatalogName(name);//设置名称
                }
                Catalogs.add(catalogVO);
            }
        }
        result.setCatalogs(Catalogs);//设置类别集合

        // 4.当前商品涉及到的所有的属性信息

        ParsedNested attr_agg = aggregations.get("attr_agg");
        ParsedLongTerms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");
        List<? extends Terms.Bucket> bucketsAttr = attr_id_agg.getBuckets();
        List<SearchResult.AttrVo> attrVos = new ArrayList<>();
        if (bucketsAttr != null && bucketsAttr.size() > 0) {
            for (Terms.Bucket bucket : bucketsAttr) {
                SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
                // 获取属性的编号
                String keyAsString = bucket.getKeyAsString();
                attrVo.setAttrId(Long.parseLong(keyAsString));

                // 又得分别获取 属性的名称 和 属性的值
                ParsedStringTerms attr_name_agg = bucket.getAggregations().get("attr_name_agg");
                // 属性的名称
                String attrName = attr_name_agg.getBuckets().get(0).getKeyAsString();
                attrVo.setAttrName(attrName);

                ParsedStringTerms attr_value_agg = bucket.getAggregations().get("attr_value_agg");
                if(attr_value_agg.getBuckets() != null && attr_value_agg.getBuckets().size() > 0 ){
                    List<String> values = attr_value_agg.getBuckets().stream().map(item -> {
                        // 属性的值
                        String keyAsString1 = item.getKeyAsString();
                        return keyAsString1;
                    }).collect(Collectors.toList());
                    attrVo.setAttrValue(values);
                }
                attrVos.add(attrVo);
            }
        }
        result.setAttrs(attrVos);//属性值的集合
        // 5. 分页信息  当前页 总的记录数  总页数
        long total = hits.getTotalHits().value;
        result.setTotal(total);// 设置总记录数  6 /5  1+1
        result.setPageNum(param.getPageNum()); // 设置当前页
        long totalPage = total % ESconstant.PRODUCT_PAGESIZE == 0 ? total / ESconstant.PRODUCT_PAGESIZE : (total / ESconstant.PRODUCT_PAGESIZE + 1);
        result.setTotalPages((int)totalPage); // 设置总的页数
        List<Integer> navs=new ArrayList<>();
        for (int i = 1; i <=totalPage ; i++) {
        navs.add(i);
        }

        result.setNavs(navs);

        return result;
    }

}
